/// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using System.Collections.ObjectModel;
using System.Threading;

namespace Microsoft.VSPowerToys.Common.TaskManager.Tests
{

    /// <summary>
    /// Contains unit tests for Task Manager
    /// </summary>
    [TestFixture]
    public class TaskManagerTests
    {

        private TaskScheduler manager = new TaskScheduler();

        private AutoResetEvent queueCompleted = new AutoResetEvent(false);

        [SetUp]
        public void SetupTest()
        {
            manager = new TaskScheduler();
            manager.QueueCompleted += new EventHandler(SignalQueueCompleted);
            MockTask.Queue.Clear();
        }

        /// <summary>
        /// Tests task manager with a single task. Result queue should contain an entry for the task once
        /// task manager completes its run.
        /// </summary>
        [Test]
        public void SingleTaskTest()
        {
            MockTask task = new MockTask(100, "TestTask");
            manager.EnqueueTask(task);
            manager.Start();
            if (!queueCompleted.WaitOne(10000, true))
            {
                Assert.Fail(Strings.NotFinishedOnTime);
            }
            manager.Stop(true);
            Assert.AreEqual(1, MockTask.Queue.Count, Strings.IncorrectNumberOfElements);
            Assert.AreEqual("TestTask", MockTask.Queue[0]);
        }

        /// <summary>
        /// Tests task manager with multiple tasks.
        /// </summary>
        [Test]
        public void MultipleTaskTest()
        {
            for (int i =0;i<5;i++) {
                MockTask task = new MockTask(i * 200, i.ToString());
                manager.EnqueueTask(task);
            }
            manager.Start();
            if (!queueCompleted.WaitOne(10000, true))
            {
                Assert.Fail(Strings.NotFinishedOnTime);
            }
            manager.Stop(true);
            Assert.AreEqual(5, MockTask.Queue.Count, Strings.IncorrectNumberOfElements);
            for (int i = 0; i < 5; i++)
            {
                Assert.AreEqual(i.ToString(), MockTask.Queue[i]);
            }
        }

        /// <summary>
        /// Tests if task manager can be stopped correctly in the middle of its process.
        /// </summary>
        [Test]
        public void TaskManagerStopTest()
        {
            for (int i = 0; i < 5; i++)
            {
                MockTask task = new MockTask(i * 200, i.ToString());
                manager.EnqueueTask(task);
            }
            manager.TaskCompleted += new EventHandler<TaskCompletedEventArgs>(
                delegate(object sender, TaskCompletedEventArgs e)
                {
                    if (e.OwnerTask.TaskId.Equals("3"))
                    {
                        manager.Stop(false);
                        this.queueCompleted.Set();
                    }
                });
            manager.Start();
            if (!queueCompleted.WaitOne(10000, true))
            {
                Assert.Fail(Strings.NotFinishedOnTime);
            }
            manager.Stop(true);
            Assert.AreEqual(4, MockTask.Queue.Count, Strings.IncorrectNumberOfElements);
            for (int i = 0; i < 4; i++)
            {
                Assert.AreEqual(i.ToString(), MockTask.Queue[i]);
            }
        }

        /// <summary>
        /// Tests if task exceptions are handled correctly
        /// </summary>
        [Test]
        public void TaskExceptionTest()
        {
            MockTask failedTask = null;
            for (int i = 0; i < 2; i++)
            {
                MockTask task = new MockTask(i * 200, "Test");
                manager.EnqueueTask(task);
                if (i == 1) failedTask = task;
            }
            manager.TaskCompleted += new EventHandler<TaskCompletedEventArgs>(
                delegate(object sender, TaskCompletedEventArgs e)
                {
                    if (e.OwnerTask == failedTask)
                    {
                        Assert.IsFalse(e.IsSuccess, "Task success information is not correct");
                        Assert.AreEqual(Strings.TestExceptionDescription, e.ExceptionRaised.Message);
                    }
                });
            manager.Start();
            if (!queueCompleted.WaitOne(10000, true))
            {
                Assert.Fail(Strings.NotFinishedOnTime);
            }
            manager.Stop(true);
        }

        /// <summary>
        /// Tests to check if task aborted event is correctly raised
        /// </summary>
        [Test]
        public void TaskAbortedEventTest()
        {
            MockTask failedTask = null;
            for (int i = 0; i < 2; i++)
            {
                MockTask task = new MockTask(i * 200, "Test");
                manager.EnqueueTask(task);
                if (i == 1) failedTask = task;
            }
            
			manager.TaskAborted += new EventHandler<TaskAbortedEventArgs>(delegate(object sender, TaskAbortedEventArgs e)
                {
                    Assert.AreEqual(e.TaskException.OwnerTask, failedTask);
                    Assert.AreEqual(Strings.TestExceptionDescription, e.TaskException.Message);
                });

            manager.Start();
            if (!queueCompleted.WaitOne(10000, true))
            {
                Assert.Fail(Strings.NotFinishedOnTime);
            }
            manager.Stop(true);
        }

        /// <summary>
        /// Tests if TaskStarting event is raised correctly
        /// </summary>
        [Test]
        public void TaskStartingEventTest()
        {
            Collection<Task> tasks = new Collection<Task>();
            for (int i = 0; i < 2; i++)
            {
                MockTask task = new MockTask(i * 200, i.ToString());
                tasks.Add(task);
                manager.EnqueueTask(task);
            }

            manager.TaskStarting += new EventHandler<TaskEventArgs>(
                delegate(object sender, TaskEventArgs eventArgs)
                {
                    Assert.IsTrue(tasks.Contains(eventArgs.Task));
					tasks.Remove(eventArgs.Task);
                });
            manager.Start();
            if (!queueCompleted.WaitOne(10000, true))
            {
                Assert.Fail(Strings.NotFinishedOnTime);
            }
            manager.Stop(true);
            Assert.AreEqual(0, tasks.Count);
        }

        /// <summary>
        /// Tests if task progress is reported correctly.
        /// </summary>
        [Test]
        public void TaskProgressTest()
        {
            MockTask task = new MockTask(100, "TestTask");
            task.ProgressAvailable += new EventHandler<TaskProgressEventArgs>(
                delegate(object sender, TaskProgressEventArgs eventArgs)
                {
                    Assert.AreSame(task, eventArgs.CurrentTask);
                    Assert.AreEqual(1, eventArgs.CurrentProgress);
                    Assert.AreEqual(2, eventArgs.MaximumProgressValue);
                });
            manager.EnqueueTask(task);
            manager.Start();
            if (!queueCompleted.WaitOne(10000, true))
            {
                Assert.Fail(Strings.NotFinishedOnTime);
            }
            manager.Stop(true);
        }

        /// <summary>
        /// Unit tests for MsiException class
        /// </summary>
        [Test]
        public void MsiExceptionUnitTests()
        {
            MockTask task = new MockTask(100, "TestTask");
            MsiException testException = new MsiException();
            testException = new MsiException("MsiExceptionUnitTests");
            testException = new MsiException("MsiExceptionUnitTests", new System.AccessViolationException());
            
            testException = new MsiException("Path to some MSI file.", 0, task);
            Assert.AreEqual(0, testException.ErrorCode, "Error code is incorrect.");
            Assert.AreEqual("Path to some MSI file.", testException.InstallFile, "Filename is incorrect.");
            Assert.AreEqual("Success", testException.Message, "Message is incorrect.");
            Assert.AreEqual(task, testException.OwnerTask, "Task is incorrect.");
            
            testException = new MsiException("Path to some MSI file.", 1602, task);
            Assert.AreEqual(1602, testException.ErrorCode, "Error code is incorrect.");
            Assert.AreEqual("Path to some MSI file.", testException.InstallFile, "Filename is incorrect.");
            Assert.AreEqual("User cancelled installation", testException.Message, "Message is incorrect.");

            testException = new MsiException("Path to some MSI file.", 1619, task);
            Assert.AreEqual(1619, testException.ErrorCode, "Error code is incorrect.");
            Assert.AreEqual("Path to some MSI file.", testException.InstallFile, "Filename is incorrect.");
            Assert.AreEqual("MSI file could not be opened", testException.Message, "Message is incorrect.");

            testException = new MsiException("Path to some MSI file.", 1620, task);
            Assert.AreEqual(1620, testException.ErrorCode, "Error code is incorrect.");
            Assert.AreEqual("Path to some MSI file.", testException.InstallFile, "Filename is incorrect.");
            Assert.AreEqual("MSI file could not be opened", testException.Message, "Message is incorrect.");

            testException = new MsiException("Path to some MSI file.", 1638, task);
            Assert.AreEqual(1638, testException.ErrorCode, "Error code is incorrect.");
            Assert.AreEqual("Path to some MSI file.", testException.InstallFile, "Filename is incorrect.");
            Assert.AreEqual("Product is already installed", testException.Message, "Message is incorrect.");

            testException = new MsiException("Path to some MSI file.", 16, task);
            Assert.AreEqual(16, testException.ErrorCode, "Error code is incorrect.");
            Assert.AreEqual("Path to some MSI file.", testException.InstallFile, "Filename is incorrect.");
            Assert.AreEqual("Unknown error code", testException.Message, "Message is incorrect.");

        }

        [TearDown]
        public void TearDownTest()
        {
            this.manager = null;
        }

        /// <summary>
        /// Test fixture tear down method: Closes AutoEventReset object
        /// </summary>
        [TestFixtureTearDown]
        public void FixtureTearDown()
        {
            this.queueCompleted.Close();
        }

        /// <summary>
        /// Sets the queueCompleted event so waiting tests can continue
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void SignalQueueCompleted(object sender, EventArgs e)
        {
            this.queueCompleted.Set();
        }

        
    }

    /// <summary>
    /// A mock task object to test functionality of task manager.
    /// </summary>
    public class MockTask : Task
    {

        private static Collection<string> queue = new Collection<string>();

        private int sleepTime;

        /// <summary>
        /// Queue to store task results
        /// </summary>
        public static Collection<string> Queue 
        {
            get 
            {
                return queue;
            }
        }

        public MockTask(int sleepTime, string id) : base(id)
        {
            this.sleepTime = sleepTime;
        }

        public override void PerformTask()
        {
            Thread.Sleep(sleepTime);
            lock (Queue)
            {
                if (Queue.Contains(this.TaskId))
                {
                    throw new TaskException(Strings.TestExceptionDescription, this);
                }
                this.ReportProgress(1, 2);
                Queue.Add(this.TaskId);
            }
        }

        public override bool IsCancellable
        {
            get { return false; }
        }
    }
}
